{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">原文地址 [blog.csdn.net](https://blog.csdn.net/weixin_34403693/article/details/89591404)\n",
    "\n",
    "概述\n",
    "--\n",
    "\n",
    "UUID，通用唯一识别码（Universally Unique Identifier）。  \n",
    "UUID 的目的是让分布式系统中的所有元素都能有唯一的辨识信息，而不需要透过中央控制端来做辨识信息的指定。  \n",
    "UUID 的标准型式包含 32 个 16 进制数字，以连字号分为五段，形式为 8-4-4-4-12 的 32 个字符。  \n",
    "示例：\n",
    "\n",
    "> 550e8400-e29b-41d4-a716-446655440000\n",
    "\n",
    "——以上内容摘自百度百科\n",
    "\n",
    "实现\n",
    "--\n",
    "\n",
    "UUID 有很多实现版本，以下是 JDK 的一个实现：\n",
    "\n",
    "```java\n",
    "    private static class Holder {\n",
    "        static final SecureRandom numberGenerator = new SecureRandom();\n",
    "    }\n",
    " \n",
    "    public static UUID randomUUID() {\n",
    "        SecureRandom ng = Holder.numberGenerator;\n",
    " \n",
    "        byte[] randomBytes = new byte[16];\n",
    "        ng.nextBytes(randomBytes);\n",
    "        randomBytes[6]  &= 0x0f;  /* clear version        */\n",
    "        randomBytes[6]  |= 0x40;  /* set to version 4     */\n",
    "        randomBytes[8]  &= 0x3f;  /* clear variant        */\n",
    "        randomBytes[8]  |= 0x80;  /* set to IETF variant  */\n",
    "        return new UUID(randomBytes);\n",
    "    }\n",
    "```\n",
    "\n",
    "用 SecureRandom 生成的 16 字节 (128bit) 随机数，用掩码打上版本和 IETF 标识。  \n",
    "实际有效随机位 122 位。关于冲突概率，可以参考笔者另一片文章，[漫谈散列函数](https://www.jianshu.com/p/ad9756fe21c8)。\n",
    "\n",
    "特征\n",
    "--\n",
    "\n",
    "UUID 的优点很明显：“**分布式**”、“**唯一**”。  \n",
    "这些优点使得 UUID 被广泛使用，尤其是分布式环境下。\n",
    "\n",
    "然而其缺点也很明显：无序，长度较长。  \n",
    "这些缺点也极大地限制了其应用范围，比如数据表的主键，通常大家都不会用 UUID。\n",
    "\n",
    "但还是有不少地方用到 UUID 的：  \n",
    "有时候想给一个对象分配一个标识，但是该对象不好提取唯一特征，然后该环境下又不好统一分配，  \n",
    "这时候很自然就想到 UUID 了，UUID 不需要以对象特征为参数，也不用担心重复（不是说不会重复，只是不用担心，就像不用担心天上掉下陨石砸到自己一样 -_-）。\n",
    "\n",
    "压缩\n",
    "--\n",
    "\n",
    "但是看着这个 36 个字节长度的 UUID，总不自觉地会想有没有优化的余地。  \n",
    "16 字节的信息，用 16 进制显示，有 32 个字符，加上分隔符，有 36 字节。  \n",
    "事实上，如果用 base64 编码这 16 个字节，可以压缩到 22 字节。\n",
    "\n",
    "```\n",
    "    public static byte[] hex2Bytes(String hex) {\n",
    "        if (hex == null || hex.isEmpty()) {\n",
    "            return new byte[0];\n",
    "        }\n",
    "        byte[] bytes = hex.getBytes();\n",
    "        int n = bytes.length >> 1;\n",
    "        byte[] buf = new byte[n];\n",
    "        for (int i = 0; i < n; i++) {\n",
    "            int index = i << 1;\n",
    "            buf[i] = (byte) ((byte2Int(bytes[index]) << 4) | byte2Int(bytes[index + 1]));\n",
    "        }\n",
    "        return buf;\n",
    "    }\n",
    " \n",
    "    private static int byte2Int(byte b) {\n",
    "        return (b <= '9') ? b - '0' : b - 'a' + 10;\n",
    "    }\n",
    " \n",
    "    public static String compressUUID(String uuid){\n",
    "        String hex = uuid.replace(\"-\", \"\");\n",
    "        byte[] bytes = FormatUtils.hex2Bytes(hex);\n",
    "        return new String(Base64.encode(bytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));\n",
    "    }\n",
    "```\n",
    "\n",
    "UUID 压缩前后：\n",
    "\n",
    "> d44979db-5c64-40f1-b47e-e7f41c4be9e7\n",
    "\n",
    "3dkJ2-z92fr9DuD9rNvp4A\n",
    "\n",
    "36 字节相对 22 字节，节约接近 40% 的长度，对于存储和传输而言，都是较大的提升；  \n",
    "虽然从可读性来说，UUID 的可读性更好。  \n",
    "在权衡可读性和性能的时候，笔者通常的想法是，如果阅读和书写比较频繁，选择可读性较好的，如果不怎么需要阅读，选择对机器友好的。  \n",
    "尤其是对于数据库存储这种情况，由于存在规模效应，显然压缩的版本更具性价比。\n",
    "\n",
    "优化\n",
    "--\n",
    "\n",
    "如果需要压缩版本的 UUID，调用 JDK 的 UUID 生成字符串，再处理成压缩版的 UUID，显然 “绕圈子” 了。  \n",
    "我们可以仿照 JDK 的写法直接生成：\n",
    "\n",
    "```java\n",
    "    public static String randomUUID() {\n",
    "        byte[] bytes = new byte[15];\n",
    "        Holder.numberGenerator.nextBytes(bytes);\n",
    "        return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);\n",
    "    }\n",
    "```\n",
    "\n",
    "15 字节的随机数，120bit, 和 JDK 的 randomUUID 效用上是差不多，然后 15 是 3 的倍数，base64 编码时不需要 PADDING；  \n",
    "生成 20 字节的字符串（15 / 3 * 4), 相对 UUID 的 36 字节，节约近一半的空间。\n",
    "\n",
    "其他\n",
    "--\n",
    "\n",
    "base64 编码有一个逼死强迫症的特点：除了常规字符 [A-Za-z0-9] 之外，需要另外两个字符才能凑够 64 个字符。  \n",
    "于是，我们看到 base64 分化了两个版本，分别以 ['+', '/'] 和 ['-', '_'] 作为补充字符的两个版本。  \n",
    "其中，后者是 **URL_SAFE** 的版本，前者编码后可能会包含'/', 而'/'是 URL 的分隔符。  \n",
    "但无论哪个版本，对于 URL 而言，有非常规字符确实确实不是很 “美观”。  \n",
    "于是，有人想出了 base62 编码。  \n",
    "base62 编码，通常用来给 long 编码还好，用来编码任意字节数组的话，效率很低。  \n",
    "不过对于 long 来说，base62 编码长度为 11 字节，而十六进制编码也只是 16 个字节，而且十六进制可读性更好。\n",
    "\n",
    "简书的文章 ID，十六进制，12 字节（48bit）。  \n",
    "![](https://upload-images.jianshu.io/upload_images/1166341-21a83e51612a0477.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)  \n",
    "12 字节的长度，可读性 OK；48bit，取值范围有两百多万亿，够用。总的来说，是比较均衡的方案。  \n",
    "我很好奇是怎么构造的：  \n",
    "随机数？可能性不大。  \n",
    "自增序列？不太像。通常纯自增序列的 ID 长度不固定，如 QQ 号。\n",
    "\n",
    "如果让我来写，有可能会混合多个因子来构造 ID。  \n",
    "例如 Twitter 的 Snowflake，混合了时间戳，机器 ID 和序列号。  \n",
    "![](https://upload-images.jianshu.io/upload_images/1166341-58aef3e09ac34f63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n",
    "\n",
    "计算机从 16 位寄存器，到 32 位，再到 64 位，就不往上涨了；  \n",
    "在当前的体系下，对于数据库存储而言，64bit 的 ID 是最适合的。\n",
    "\n",
    "总结\n",
    "--\n",
    "\n",
    "*   尽量用整型的 ID；\n",
    "*   如果要用 UUID，尽量用压缩的版本；\n",
    "*   MD5 也是 128bit, 作为字符串传输和存储时，base64 编码要优于 16 进制。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
